#include "Tablero.h"
#include "Caballo.h"
#include "Reina.h"
#include "Rey.h"
#include "Pieza.h"
#include "Parser.h"

Tablero::Tablero() {
	for (int i = 0; i < 8; i++)
		for (int j = 0; j < 8; j++)
			casilleros[i][j] = NULL;
}

Pieza *Tablero::getPieza(Posicion pos) const {
	return this->casilleros[pos.getY()][pos.getX()];
}

/* Dadas una posicion origen, una de destino y una pieza de ajedrez,
 * ubica a la pieza en la posicion correspondiente dentro del tablero. */

void Tablero::moverPieza(Posicion origen,
						 Posicion destino,
						 Pieza *unaPieza) {
	setPieza(destino, unaPieza);
	this->casilleros[origen.getY()][origen.getX()] = NULL;
}

void Tablero::setPieza(Posicion destino, Pieza* unaPieza) {
	this->casilleros[destino.getY()][destino.getX()] = unaPieza;
	unaPieza->setPosicion(destino);
}

void Tablero::setCasilleroVacio(Posicion posicion) {
	this->casilleros[posicion.getY()][posicion.getX()] = NULL;
}

/* Dada una posicion del tablero, indica si en la misma se encuentra
 * una pieza o no. */

bool Tablero::casillaNoOcupada(Posicion pos) const {
	if (casilleros[pos.getY()][pos.getX()])
		return false;
	else
		return true;
}

/* Dada una posicion, permite observar los movimientos posibles segun la pieza
 * que se encuentre en dicha posicion. */

Lista* Tablero::observarMovimientos(Posicion pos) const {
	return this->getPieza(pos)->movimientosPosibles(*this);
}

/* Dados un color y un tipo de enroque, realiza los movimientos adecuados en el
 * tablero. */

void Tablero::realizarEnroque(tColor color, int tipoEnroque) {
	Pieza *rey, *torre;
	Posicion nuevaPosicionRey, nuevaPosicionTorre,
						antiguaPosicionRey, antiguaPosicionTorre;
	antiguaPosicionRey.setX(4);
	if(tipoEnroque == 1) {  // enroque corto.
		nuevaPosicionRey.setX(6);
		nuevaPosicionTorre.setX(5);
		antiguaPosicionTorre.setX(7);
	}
	else {  // enroque largo.
		nuevaPosicionRey.setX(2);
		nuevaPosicionTorre.setX(3);
		antiguaPosicionTorre.setX(0);
	}
	if(color == BLANCO) {
		nuevaPosicionRey.setY(7);
		nuevaPosicionTorre.setY(7);
		antiguaPosicionRey.setY(7);
		antiguaPosicionTorre.setY(7);
	}
	else {
		nuevaPosicionRey.setY(0);
		nuevaPosicionTorre.setY(0);
		antiguaPosicionRey.setY(0);
		antiguaPosicionTorre.setY(0);
	}
	rey = this->getPieza(antiguaPosicionRey);
	torre = this->getPieza(antiguaPosicionTorre);
	this->moverPieza(antiguaPosicionRey, nuevaPosicionRey, rey);
	this->moverPieza(antiguaPosicionTorre, nuevaPosicionTorre, torre);
}

/* Dados un caracter, que representa a un tipo de pieza, y una
 * posicion, realiza la promocion (al tipo correspondiente) de
 * la pieza que se ubica en la posicion dentro  del tablero.*/

void Tablero::promocionarPieza(char tipo, Posicion posicion) {
	tColor color = this->getPieza(posicion)->getColor();
	switch (tipo) {
		case TORRE:
			delete(this->getPieza(posicion));
			setPieza(posicion, new Torre(color, posicion));
			break;
		case CABALLO:
			delete(this->getPieza(posicion));
			setPieza(posicion, new Caballo(color, posicion));
			break;
		case ALFIL:
			delete(this->getPieza(posicion));
			setPieza(posicion, new Alfil(color, posicion));
			break;
		case REINA:
			delete(this->getPieza(posicion));
			setPieza(posicion, new Reina(color, posicion));
			break;
		case REY:
			delete(this->getPieza(posicion));
			setPieza(posicion, new Rey(color, posicion));
			break;
		}
}

Tablero::~Tablero() {
	for (int i = 0; i < MAX_COORD; i++)
		for (int j = 0; j < MAX_COORD; j++)
			if (casilleros[i][j])
				delete (casilleros[i][j]);
}

/*Dadas dos posiciones dentro del tablero, indica si pertenecen a
 *piezas del mismo jugador. */

bool Tablero::piezaContrario(Posicion unaPos, Posicion otraPos) const {
	tColor colorUno = getPieza(unaPos)->getColor();
	tColor colorDos = getPieza(otraPos)->getColor();
	return colorUno != colorDos;
}

/* Dadas una movida, el color del turno, si se desea o no evaluar movimientos, el tipo de enroque y
 * si se desea promocionar, se realizan los movimientos solicitados. */

void Tablero::procesarTurno(const std::string& turno, tColor color,
							bool evaluarMovimientos, int enroque, bool promociona) {
	Posicion inicial, final;
	Lista *lista = NULL;
	if(enroque) this->realizarEnroque(color, enroque);
	else {
		if(turno.at(0) >= MINUSCULAS) {  // pieza a mover es un peon.
			inicial.cambiarCoordenadasNumericas(turno.at(0), turno.at(1));
			final.cambiarCoordenadasNumericas(turno.at(2), turno.at(3));
		}
		else {  // pieza a mover mover no es peon
			inicial.cambiarCoordenadasNumericas(turno.at(1), turno.at(2));
			final.cambiarCoordenadasNumericas(turno.at(3), turno.at(4));
		}
		if(evaluarMovimientos) {
			lista = (this->observarMovimientos(inicial));
			std::cout << *lista;
			delete lista;
		}
		delete (this->getPieza(final));
		this->moverPieza(inicial, final, this->getPieza(inicial));
		if((turno.length() > 4) && promociona)
			this->promocionarPieza(turno.at(turno.length()-1), final);
	}
}

std::ostream &operator<<(std::ostream &stream, Tablero &tablero) {
	Posicion pos;
	for (int i = 0; i < MAX_COORD; i++) {
		for (int j = 0; j < MAX_COORD; j++) {
			pos.setX(j);
			pos.setY(i);
			if (!tablero.casillaNoOcupada(pos))
				stream << *(tablero.getPieza(pos));
			else
				stream << CASILLA_VACIA;
			}
		stream << "\n";
		}
	return stream;
}

/* Lee la primer linea del archivo pgn y arma el tablero correspondiente. */

std::istream& operator >>(std::istream &stream, Tablero &tablero) {
	std::string fila;
	int numeroFila = 0;
	while (true) {
		getline(stream, fila, DELIMITADOR_FILA);
		Parser::armarFila(fila, &tablero, numeroFila);
		numeroFila++;
		if (numeroFila == 7) {
			getline(stream, fila, ' ');
			Parser::armarFila(fila, &tablero, numeroFila);
			break;
		}
	}
	return stream;
}
